#region License

// Copyright (c) 2013, ClearCanvas Inc.
// All rights reserved.
// http://www.clearcanvas.ca
//
// This file is part of the ClearCanvas RIS/PACS open source project.
//
// The ClearCanvas RIS/PACS open source project is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// The ClearCanvas RIS/PACS open source project is distributed in the hope that it
// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along with
// the ClearCanvas RIS/PACS open source project.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections.Generic;
using System.Text;
using ClearCanvas.Common;

namespace ClearCanvas.ImageViewer.RoiGraphics.Analyzers
{
	/// <summary>
	/// Extension point for <see cref="IRoiAnalyzer"/>s that are automatically discovered and invoked by the <see cref="RoiCalloutGraphic"/>.
	/// </summary>
	public sealed class RoiAnalyzerExtensionPoint : ExtensionPoint<IRoiAnalyzer>
	{
		/// <summary>
		/// Creates new instances of all available <see cref="IRoiAnalyzer"/>s extending <see cref="RoiAnalyzerExtensionPoint"/>.
		/// </summary>
		/// <returns>An enumeration of <see cref="IRoiAnalyzer"/>s.</returns>
		public static IEnumerable<IRoiAnalyzer> CreateRoiAnalyzers()
		{
			SortedList<string, IRoiAnalyzer> extensions = new SortedList<string, IRoiAnalyzer>();
			foreach (IRoiAnalyzer roiAnalyzer in new RoiAnalyzerExtensionPoint().CreateExtensions())
			{
				roiAnalyzer.Units = RoiSettings.Default.AnalysisUnits;
				extensions.Add(roiAnalyzer.GetType().FullName, roiAnalyzer);
			}
			return extensions.Values;
		}
	}

	/// <summary>
	/// Enumerated values for the type of ROI analysis to perform.
	/// </summary>
	public enum RoiAnalysisMode
	{
		/// <summary>
		/// Indicates that normal analysis should be performed.
		/// </summary>
		Normal = 0,

		/// <summary>
		/// Indicates that the analysis is being performed in response to live changes, and that only fast analysis should be performed.
		/// </summary>
		Responsive = 1
	}

	/// <summary>
	/// Result generated by Roi Analyzers
	/// </summary>
	public interface IRoiAnalyzerResult
	{
		/// <summary>
		/// Name of the result (eg, Area)
		/// </summary>
		string Name { get; }

		/// <summary>
		/// The raw value
		/// </summary>
		object Value { get; }

		/// <summary>
		/// Converts the result into a string
		/// </summary>
		/// <returns></returns>
		string SerializedAsString();
	}

	public abstract class RoiAnalyzerResultBase
	{
		/// <summary>
		/// Name of the result (eg, Area, StdDev)
		/// </summary>
		public string Name { get; protected set; }

		public object Value { get; protected set; }
		public string Units { get; protected set; }

		public bool HasValue { get; protected set; }
		public bool HasUnits { get; protected set; }

		protected RoiAnalyzerResultBase(string name)
		{
			Name = name;
		}

		public abstract string SerializedAsString();
	}

	/// <summary>
	/// Special class of analyzing results which have a signle value
	/// </summary>
	public class SingleValueRoiAnalyzerResult : RoiAnalyzerResultBase, IRoiAnalyzerResult
	{
		public override string SerializedAsString()
		{
			return SerializedValue;
		}

		private string SerializedValue { get; set; }

		public SingleValueRoiAnalyzerResult(string name, string unit, object value, string serializedValue)
			: base(name)
		{
			Name = name;
			Units = unit;
			Value = value;
			SerializedValue = serializedValue;

			HasValue = true;
			HasUnits = unit != null;
		}
	}

	/// <summary>
	/// Special class of analyzing results which have no value and unit, usually because of an error
	/// </summary>
	public sealed class RoiAnalyzerResultNoValue : RoiAnalyzerResultBase, IRoiAnalyzerResult
	{
		private readonly string _serializedText;

		public RoiAnalyzerResultNoValue(string name, string serializedText)
			: base(name)
		{
			_serializedText = serializedText;

			HasValue = false;
			HasUnits = false;
		}

		public override string SerializedAsString()
		{
			return _serializedText;
		}
	}

	/// <summary>
	/// Special class of analyzing results which contains multiple values
	/// </summary>
	public class MultiValueRoiAnalyzerResult : RoiAnalyzerResultBase, IRoiAnalyzerResult
	{
		private string _formattedHeader;
		private bool _showHeader = true;

		private readonly List<IRoiAnalyzerResult> _values;
		private bool _dirty = true;
		private string _serializedValue;

		public MultiValueRoiAnalyzerResult(string name) :
			this(name, new List<IRoiAnalyzerResult>()) {}

		public MultiValueRoiAnalyzerResult(string name, IEnumerable<IRoiAnalyzerResult> values)
			: base(name)
		{
			Name = name;
			_values = new List<IRoiAnalyzerResult>(values);
			Value = _values;

			HasUnits = false;
			HasValue = false;
		}

		public virtual string FormattedHeader
		{
			get { return _formattedHeader ?? Name; }
			set { _formattedHeader = value; }
		}

		public bool ShowHeader
		{
			get { return _showHeader; }
			set
			{
				_showHeader = value;
				_dirty = true;
			}
		}

		public override string SerializedAsString()
		{
			if (Value == null)
				return String.Empty;

			if (!_dirty)
				return _serializedValue;

			StringBuilder sb = new StringBuilder();
			if (ShowHeader)
				sb.AppendLine(String.Format("{0}:", FormattedHeader));

			IEnumerable<IRoiAnalyzerResult> values = Value as IEnumerable<IRoiAnalyzerResult>;
			if (values == null)
			{
				return string.Empty;
			}

			foreach (var value in values)
			{
				if (ShowHeader) sb.AppendLine(String.Format("   {0}", value.SerializedAsString()));
				else sb.AppendLine(String.Format("{0}", value.SerializedAsString()));
			}

			_serializedValue = sb.ToString();
			_dirty = false;
			return _serializedValue;
		}

		public void Add(IRoiAnalyzerResult value)
		{
			_values.Add(value);
			_dirty = true;
		}
	}

	/// <summary>
	/// Interface for all ROI analyzers.
	/// </summary>
	public interface IRoiAnalyzer
	{
		/// <summary>
		/// Gets or sets the base unit of measurement in which analysis is performed.
		/// </summary>
		Units Units { get; set; }

		/// <summary>
		/// Checks if this analyzer class can analyze the given ROI.
		/// </summary>
		/// <remarks>
		/// Implementations should return a result based on the type of ROI, not on the particular current state of the ROI.
		/// </remarks>
		/// <param name="roi">The ROI to analyze.</param>
		/// <returns>True if this class can analyze the given ROI; False otherwise.</returns>
		bool SupportsRoi(Roi roi);

		/// <summary>
		/// Analyzes the given ROI.
		/// </summary>
		/// <param name="roi">The ROI being analyzed.</param>
		/// <param name="mode"></param>
		/// <returns><see cref="IRoiAnalyzerResult"/> containing the analysis result, if one is proceduced.</returns>
		/// <remarks>
		/// The other overload returns the same information in a formatted string.
		/// </remarks>
		IRoiAnalyzerResult Analyze(Roi roi, RoiAnalysisMode mode);
	}
}